#include "xml_node.h"
#include "converter.h"
#include "str.h"
#include "exceptions.h"
#include "useful_functions.h"
#include "global_variables.h"

string xml_node::opening_tag = "<";
string xml_node::closing_tag = ">";

////

static const string &get_next_one(const list<string> &lst, size_t i) {
  if (i >= lst.size())
    return global_empty_string;

  list<string>::const_iterator it = lst.begin();
  advance(it, i);
  return *it;
}

static xml_node *xml_path_search(xml_node *xn, const string &s1, const list<string> &lst, size_t i) {
  if (xn == 0 || s1.empty() == true)
    return 0;

  const list<xml_node*> &xns = xn->get_children();
  for (list<xml_node*>::const_iterator it = xns.begin(); it != xns.end(); ++it) {
    if ((*it)->get_name() == s1) {
      const string &s2 = get_next_one(lst, i);

      if (s2.empty() == false) {
        xml_node *tmp_xn = xml_path_search(*it, s2, lst, i + 1);
        if (tmp_xn != 0)
          return tmp_xn; // found

      } else
        return *it; // found
    } // if
  } // for

  return 0;
}

////

void xml_node::set_opening_tag(const string &tag) {
  opening_tag = tag;
}

void xml_node::set_closing_tag(const string &tag) {
  closing_tag = tag;
}

const string &xml_node::operator()(search_element se, bool case_sensitive, const string &str_search) const {
  if (se == search_attributes) {

    const safe_pointer<xml_attribute> xa = search_by_attribute_name(str_search, case_sensitive);
    if (xa.is_null() == true)
      return global_empty_string;
    else
      return xa->content;

  } else if (se == search_children) {

    const safe_pointer<xml_node> xn = search_by_tag_name(str_search, case_sensitive);
    if (xn.is_null() == true)
      return global_empty_string;
    else
      return xn->content;

  } else
    return global_empty_string;
}

const string &xml_node::get_attribute_content(const string &attribute_name, bool case_sensitive) const {
  const safe_pointer<xml_attribute> xa = search_by_attribute_name(attribute_name, case_sensitive);

  if (xa.is_null() == true) {
    string msg = "Attribute name '%1' not found!";
    str::format(msg, arg<string>(attribute_name));
    throw xml_exception(msg);
  } else
    return xa->content;
}

const safe_pointer<xml_node> xml_node::search_by_path(const string &path) const {
  list<string> lst;
  str::split(path, "/", lst);

  if (lst.empty() == true)
    return 0;

  xml_node *tmp_xn = 0;
  if (this->name == *(lst.begin()))
    tmp_xn = const_cast<xml_node*>(this);
  else {
    safe_pointer<xml_node> sp_xn = this->search_by_tag_name(*(lst.begin()));
    if (sp_xn.is_null() == false)
      tmp_xn = sp_xn.operator->();
    else
      tmp_xn = 0;
  } // else

  if (tmp_xn == 0 || lst.size() == 1)
    return tmp_xn;

  size_t i = 2;
  return xml_path_search(tmp_xn, *(++(lst.begin())), lst, i);
}

const safe_pointer<xml_node> xml_node::search_by_tag_name(const string &tag_name, bool case_sensitive, bool recursive) const {
  safe_pointer<xml_node> result;

  list<xml_node*>::const_iterator end_iter = children.end();
  for (list<xml_node*>::const_iterator it = children.begin(); it != end_iter; ++it) {
    if (str::equals((*it)->name, tag_name, case_sensitive) == true)
      return (*it);
    else if (recursive == true) {
      result = (*it)->search_by_tag_name(tag_name, case_sensitive, recursive);
      if (result.is_null() == false)
        return result;
    } // else
  } // for

  return 0; // not found
}

const safe_pointer<xml_attribute> xml_node::search_by_attribute_name(const string &attribute_name, bool case_sensitive) const {
  list<xml_attribute*>::const_iterator end_it = attributes.end();

  for (list<xml_attribute*>::const_iterator it = attributes.begin(); it != end_it; ++it) {
    if (str::equals((*it)->name, attribute_name, case_sensitive) == true)
      return (*it);
  } // for

  return 0;
}

bool xml_node::remove_by_tag_name(const string &tag_name, bool case_sensitive, bool recursive) {
  list<xml_node*>::const_iterator end_iter = children.end();
  for (list<xml_node*>::iterator it = children.begin(); it != end_iter; ++it) {
    if (str::equals((*it)->name, tag_name, case_sensitive) == true) {
        delete (*it);
        children.erase(it);
        return true;
    } else if (recursive == true) {
      if ((*it)->remove_by_tag_name(tag_name, case_sensitive, recursive) == true)
        return true;
    } // else
  } // for

  return false;
}

bool xml_node::remove_by_attribute_name(const string &attribute_name, bool case_sensitive) {
  safe_pointer<xml_attribute> xa = search_by_attribute_name(attribute_name, case_sensitive);
  if (xa.is_null() == true)
    return false;

  xml_attribute *p = xa.operator->();
  attributes.remove(p);
  delete p;
  p = 0;
  xa = 0;

  return true;
}

void xml_node::get_all_attributes(string &output) const {
  output.resize(0);
  if (this->attributes.empty() == true)
    return;
  else
    output.reserve(this->attributes.size() * 8);

  list<xml_attribute*>::const_iterator end_it = attributes.end();

  for (list<xml_attribute*>::const_iterator it = attributes.begin(); it != end_it; /* nothing to do */) {
    output += (*it)->name;
    output += "=\"";
    output += (*it)->content;
    output += '"';

    ++it;
    if (it != end_it)
      output += ' ';
  } // if
} // xml_node::get_all_attributes

void xml_node::get_all_children(string &output, bool pretty_print, bool include_attributes, const string &spacer) const {
  output.resize(0);
  if (this->children.empty() == true)
    return;
  else
    output.reserve(this->children.size() * 8 + this->attributes.size() * 8);

  static const string original_space = spacer;

  if (pretty_print == true)
    output += spacer;

  string str_children;

  list<xml_node*>::const_iterator end_it = children.end();
  for (list<xml_node*>::const_iterator it = children.begin(); it != end_it; /* nothing to do */) {
    output += opening_tag;
    output += (*it)->name;

    if (include_attributes == true) {
      string str_attributes;

      (*it)->get_all_attributes(str_attributes);
      if (str_attributes.empty() == false) {
        output += ' ';
        output += str_attributes;
      } // if
    } // if

    if ((*it)->content.empty() == true && (*it)->children.size() == 0) { // empty element without any child
      output += " /";
      output += closing_tag;
    } else {
      output += closing_tag;

      if (pretty_print == true) {
        output += '\n';
        if ((*it)->content.empty() == false) {
          output += spacer;
          output += original_space;
        } // if
      } // if

      output += (*it)->content;

      if (pretty_print == true) {
        (*it)->get_all_children(str_children, pretty_print, include_attributes, spacer + original_space);

        if ((*it)->content.empty() == false && str_children.empty() == false)
          output += '\n';
      } else
        (*it)->get_all_children(str_children, pretty_print, include_attributes, global_empty_string);

      output += str_children;

      if (pretty_print == true) {
        output += '\n';
        output += spacer;
      } // if

      output += opening_tag;
      output += '/';
      output += (*it)->name;
      output += closing_tag;
    } // else

    ++it;
    if (pretty_print == true && it != end_it) {
      output += '\n';
      output += spacer;
    } // if
  } // for
} // xml_node::get_all_children

void xml_node::set_content(const string &content) {
  this->content = content;
} // xml_node::set_content

const string &xml_node::get_content() const {
  return this->content;
} // xml_node::get_content

void xml_node::set_name(const string &name) {
  this->name = name;
} // xml_node::set_name

const string &xml_node::get_name() const {
  return this->name;
} // xml_node::get_name

const list<xml_attribute*> &xml_node::get_attributes() const {
  return this->attributes;
} // xml_node::get_attributes

const list<xml_node*> &xml_node::get_children() const {
  return this->children;
} // xml_node::get_children

void xml_node::to_string(string &output, bool pretty_print, bool include_attributes, const string &spacer) const {
  output = opening_tag;
  output += name;

  if (include_attributes == true && attributes.empty() == false) {
    string attributes_str;

    get_all_attributes(attributes_str);
    output += ' ';
    output += attributes_str;
  } // if

  output += closing_tag;

  if (pretty_print == true) {
    output += '\n';
    if (content.empty() == false)
      output += spacer;
  } // if

  output += content;

  if (pretty_print == true && content.empty() == false)
    output += '\n';

  if (children.empty() == false) {
    string children_str;

    get_all_children(children_str, pretty_print, include_attributes, spacer);
    output += children_str;

    if (pretty_print == true)
      output += '\n';
  } // if

  output += opening_tag;
  output += '/';
  output += name;
  output += closing_tag;
} // xml_node::to_string

void xml_node::add_child(xml_node *xn) {
  if (xn != 0)
    children.push_back(xn);
} // xml_node::add_child

void xml_node::add_attribute(xml_attribute *xa) {
  if (xa != 0)
    attributes.push_back(xa);
} // xml_node::add_attribute

void xml_node::destroy(destruction_type dt) {
  if (dt == destroy_all || dt == destroy_attributes) {
    for_each(attributes.begin(), attributes.end(), deleter());
    attributes.clear();
  } // if

  if (dt == destroy_all || dt == destroy_children) {
    for_each(children.begin(), children.end(), deleter());
    children.clear();
  } // if
} // xml_node::destroy

xml_node &xml_node::operator=(const xml_node &xn) {
  if (&xn == this)
    return *this;

  this->destroy();

  this->name = xn.name;
  this->content = xn.content;

  list<xml_attribute*>::const_iterator end_it1 = xn.attributes.end();
  for (list<xml_attribute*>::const_iterator it = xn.attributes.begin(); it != end_it1; ++it)
    this->attributes.push_back(new xml_attribute(*(*it))); // hard copy of attributes

  list<xml_node*>::const_iterator end_it2 = xn.children.end();
  for (list<xml_node*>::const_iterator it = xn.children.begin(); it != end_it2; ++it)
    this->children.push_back(new xml_node(*(*it))); // hard copy of children

  return *this;
} // xml_node::operator=

xml_node::xml_node(const xml_node &xn) : name(xn.name), content(xn.content) {
  list<xml_attribute*>::const_iterator end_it1 = xn.attributes.end();
  for (list<xml_attribute*>::const_iterator it = xn.attributes.begin(); it != end_it1; ++it)
    this->attributes.push_back(new xml_attribute(*(*it))); // hard copy of attributes

  list<xml_node*>::const_iterator end_it2 = xn.children.end();
  for (list<xml_node*>::const_iterator it = xn.children.begin(); it != end_it2; ++it)
    this->children.push_back(new xml_node(*(*it))); // hard copy of children
} // xml_node::xml_node

xml_node::xml_node() : name(global_empty_string), content(global_empty_string) {
  // nothing to do
} // xml_node::xml_node

xml_node::xml_node(const string &name, const string &content) : name(name), content(content) {
  // nothing to do
} // xml_node::xml_node

xml_node::~xml_node() {
  destroy(destroy_all);
} // xml_node::~xml_node
